#include "HttpServer.hpp"
#include "Err.hpp"
#include "Util.hpp"
#include <memory>
#include <unordered_map>
#include <vector>

const std::string SEP = "\r\n";

const std::string page_404 = "./wwwroot/err_404.html";

// const std::string path = "./wwwroot/index.html";

// // 一般一个webserver，不做特殊说明，如果用户之间默认访问‘/’, 我们绝对不能把整站给对方
// // 需要添加默认首页！！而且，不能让用户访问wwwroot里面的任何一个目录本身，也可以给每一个目录都带上一个默认首页！
const std::string defaultHomePage = "index.html"; // 默认首页
const std::string webRoot = "./wwwroot";          // web根目录



class HttpRequest //
{
public:
    HttpRequest() : path_(webRoot)
    {
    }

    ~HttpRequest() {}

    void Print()
    {
        logMessage(Debug, "method: %s, url: %s, version: %s",
                   method_.c_str(), url_.c_str(), httpVersion_.c_str());
        for (const auto &line : body_)
            logMessage(Debug, "-%s", line.c_str());
        logMessage(Debug, "path: %s", path_.c_str());
        logMessage(Debug, "suffix_: %s", suffix_.c_str());
    }

public:
    std::string method_;            // 请求方法
    std::string url_;               // 请求什么资源
    std::string httpVersion_;       // 请求版本
    std::vector<std::string> body_; // 变成结构体数据

    std::string path_; // 真正想访问的资源

    std::string suffix_; // 后缀
};

HttpRequest Deserialize(std::string &message) // 把字符串反序列化解释成HttpRequest
{
    HttpRequest req;
    std::string line = Util::ReadOneLine(message, SEP);                       // 读第一行，循环式调用，读第一行，第二行，第三行，直到空行，就可以把报头放到vector里了
    Util::ParseRequestLine(line, &req.method_, &req.url_, &req.httpVersion_); // 解析他的请求行

    while (!message.empty()) // 继续读取，把massage读完了，就解析完了
    {
        line = Util::ReadOneLine(message, SEP);
        req.body_.push_back(line);
    }

    req.path_ += req.url_; //"wwwroot/a/b/c.html"
    if (req.path_[req.path_.size() - 1] == '/')
        req.path_ += defaultHomePage; // 请求过来只有一个'/'就自动拼接一个默认首页

    auto pos = req.path_.rfind(".");
    if (pos == std::string::npos)
        req.suffix_ = ".html";
    else
        req.suffix_ = req.path_.substr(pos); // 从找到的位置，截取到结尾
    return req;
}

std::string GetContentType(const std::string &suffix)
{
    std::string content_type = "Content-Type: ";
    if (suffix == ".html" || suffix == ".htm")
        content_type + "text/html";
    else if (suffix == ".css")
        content_type += "text/css";
    else if (suffix == ".js")
        content_type += "application/x-javascript";
    else if (suffix == ".png")
        content_type += "image/png";
    else if (suffix == ".jpg")
        content_type += "image/jpeg";
    else
    {
    }
    return content_type + SEP;
}

// 我们对应的请求，我们给浏览器返回结果，怎么知道返回的是正确的，所以有http状态码
// 当你访问某个服务器上的某个资源时，这个资源不存在，属于客户端错误还是服务器错误？
// 请求也分正常请求和不正常请求，比如问爸要一个亿，服务器上的资源是有限的，所以404报错是客户端的错误
// 要不要告诉客户端是错误的？要，所以会返回404的页面
// 5开头，一般是服务器错误，比如我们今天在做请求，服务器里面可能会创建进程或线程处理失败，这就是服务器内部错误，或者一些原因，服务器响应失败
// 即使公司内部是服务器错了，也不会给你发是5开头的错误码，一般会发1或2开头，丢人
// http中的状态码，不同的浏览器支持的层度不一样，程序员写的时候不参考http协议
// 在没手机的时候，所有人上网都要使用浏览器，如果一个浏览器被全世界的人所接受，
// 全世界的人上网的第一件事，就是打开我的浏览器，如果我想让哪个搜索引擎内置为浏览器首页，我不想给用户呈现什么结果，浏览器就可能会给用户筛选拿到的结果
// 这是很可怕的事情，比如我做了一款浏览器，也有一个搜索引擎，而我不用你的，我用其他搜索引擎，把你的搜索引擎内置成我们公司的，这就会让一个互联网公司垮了
// 不过这是pc时代，所以一家公司做浏览器，也会做搜索引擎，自己要有独立的浏览器和搜索引擎，不让其他公司成为我的上游
// 360不是，搜狗也不是浏览器，国内的浏览器都是把被人的拿来，包装成自己的品牌

// 3开头的状态码，重定向状态码
// 概念：有个客户端和服务器，因为某些原因服务器地址换了， 对应网站变了，可是老用户不知道，
// 所以老用户访问的时候，请求之后，服务器不对这个客户端提供服务，只是告诉你当前需要访问其他地址，
// 有个学校东门，在东门有加吃饭的地方，但是学校东门路况不好，过来二三月，走到吃饭的地方，发现门上有贴张，
// 在修理路，我们临时搬到西们，所以重定向功能是通过服务的一方告诉你，我们店门没办法提供服务了，由你决定去不去
// 过了半年后，是去西门还是东门，但是我不知道路修好没，所以我还是去东门看看，看了后，然后再跑到西门，
// 因为我不知道什么时候搬过来，这就是临时重定向，我的认知始终是先去老的店门
// 后来，老板在西门干久了，生意好，想着西门开，就回老店，贴三个月之内，我们永久搬到西门，
// 所以此时，我们去东门看到这张纸，就以后都会去西门了
// 临时重定向不更改浏览器的如何地址信息
// 永久重定向，回更改浏览器的本地书签
// server 会响应301 302 307 + Location + 新的地址
// 301：永久重定向
// 302 307：临时重定向
// 以302为例

// 重定向什么时候用？
// 1、一个网站，不仅仅是人在访问，由可能有其他的程序也在访问，比如爬虫，搜索引擎每时每刻都在爬全网的数据
// 2、搜出来一个条目，点击，这个网站过期了，打不开了
//  一般用在搜索引擎爬全网的资源，会用重定向，爬的网站会经常更新，所以有临时重定向

// 关于http的会话保持功能
// http本身是无状态的：不考虑缓存的问题，http今天访问file1，之后，还想访问，http并不知道你访问过，还会重新请求
// 登录哔哩哔哩，基于http已经登录了一个网站，关闭后，再访问，还是登录的，http不是无状态的吗，为什么还是认识我？
//  无状态主要是用于超文本传输的,本身并不会去维护你这个用户是否在线做过多工作，它本身不做，并不代表它不提供这个功能
// http不直接做这个工作，但是用户需要这个功能，保证登录后，会一直都在，这就是会话功能
// 要有用户是否在线要持续的记录下来，所以有会话保持功能，会写入一些cookie信息，就有cookie加session功能，就是会话保持功能
// 服务器上有非常多的资源，如果今天我要访问一个资源，要VIP才能访问，所以我买了个VIP
// 然后我去访问它了，服务器识别到我是VIP，就发给我资源，只是发和收，就是http的功能
// 下次我还想访问其他资源呢，http说你是谁啊，就还要再登录，
// 所以第一次访问VIP资源，服务器发现没登录，就会返回去要求登录
// 登录成功了，就会把我的账号密码提回去，服务器端就对我的账号密码做认证
// 做认证，之前肯定我注册过，就会去查我的账号注册过，
// 认证成功后，就会返回客户端一个认证通过的消息，可能返回一个301：Location: https//xxx，
// 下次再访问时，它怎么知道我是谁，
// 所以浏览器当我认证通过时，会通过一下http选项(set-cooke)，把我的私人信息写到https的响应里，发给client
// 1、将response中响应的cookie信息在本地进行保存（内存级，文件级）
// 所以以后访问请求都会携带一个cookie信息，不用用户手动操作，这是浏览器自动做的，
// 服务器会自动进行认证，client访问每一个资源的时候，都需要认证的

// 黑客盗取cookie信息，就以你的cookie登录
// 一旦我的cookie泄漏了，就被别人利用了，怎么解决呢？
// 所以服务器上有很多资源，client也来了，访问服务器资源，服务器叫先登录，会给client一个form表单，那我就以post的方式登录了，
// 以前是因为把我的信息保持在cookie里，这次我就先在服务器形成一个session对象，用当前用户的基本信息填充,
// 就有了一个内存级或者文件级的，通过办法，形成一个session id，然后把id写回给client，证明登录成功了，id是唯一的，
// client收到了id，我本地也有cookie文件，只保存session id，是有保存时间限制的，一天或几天，
// 之后在访问资源，都有携带一个http request cookie：session id，就给服务器推上来，每次推上来，就只需要确认是否在很多session id池当中，
// 存在，就不用再登录了
// 然后黑客来了，给你client中放木马，拿到你的cookie，我也可以访问了，
// 我在去访问时，也有session id，也可以访问，也会存在，
// 但是我们的信息已经被server服务器维护了，你试试攻击server服务器，这一般都是腾讯这些大公司，其一用户信息不在泄漏
// 其二，我们要正确认识cookie加session的策略，不是防范客户用户被盗取的，也不是冒认客户去访问某某网站的，也做不到
// 因为普通的小白，很难去保护自己的数据被盗用，所以我们很难去管理所有人的电脑，
// 但是在公司层面，能保证把自己的东西管理好，
// 服务器放虽然我无法确认你冒认我，但我识别出来，你冒认了，我立马就可以解决这个客户，
// 所以1、要识别出来，2、解决。其实很难解决，不可能你去把用户密码改了，
// 即使识别出来了，也很难去解决，最多是冻结，
// 所以这个账号在新疆，立马跑到北京，所以这个账号是有问题的，这个账号发生异地登录，
// 就强制你下线，重新登录，黑客只有你的cookie信息，没有你的账号密码，
// 然后识别，比如这个QQ好友一天之内都在线了，在数据层面，
// 如何解决，就直接将session id失效，就无法登录，就不向上面，怎么失效，只有改别人密码
// 所以这种方案不能杜绝client的cookie丢失的问题，但是可以提出来一下解决方案，对异常client识别出来，解决，



// GET和POST方法一般有浏览器客户发起的，它会构建一个http request，携带的方法/GET和POST

class Session // 大公司一般有个组件，把所有Session保存起来，redis->Session
{
    public:
    std::string name;
    std::string passwd;
    uint64_t loinTime;
    //用户的其他信息
    int status;//在线状态还是离线状态
    int fd;
    int sesisonid;//在指定的目录下，形成一个sesisonid
    
};

std::unordered_map<int, Session*> sessions;//insert是内存级的，没有redis效率高

// bool Login(std::string &message)//写了一个伪代码，给client返回去
// {
//     // post-> 正文包含账号密码
//     std::string name; 
//     std::string passwd;

//     if(check(name, passwd))//在服务器后端检测
//     {
//         Session* session = new Session(name, passwd...);
//         int random = rand();
//         sessions.insert(std::pair<int, Session*>(random, session));//insert是内存级的
//     }

//     http response 
//     Set-Cookie: sessionid=random;
// }


std::string HandlerHttp(std::string &message) // 收到一个请求，我无脑给你响应
{
    // 1. 读取请求
    // 确信，request一定是一个完整的http请求报文
    // 给别人返回的是一个http response
    std::cout << "----------------------------------------" << std::endl;
    std::cout << message << std::endl; // 如果是post方法，就把收到的全部报文给打印出来
    // 只要找到哪一行是空行，就说明把报头读完了，可是我怎么知道有效载荷有多大呢？
    // 所以，请求和响应中报头有一个属性：Content-Length：Body的长度

    // 暂时不要了，试试重定向功能
    //  2. 反序列化和分析请求
    //  下一步，他要什么，我给他什么，不是我无脑给你响应，去指定的wwwroot目录下，去找请求的资源所以有了反序列化
    //  HttpRequest req = Deserialize(message);
    //  req.Print();
    //  // 3. 使用请求
    //  std::string body;
    //  // std::string path = webRoot;
    //  // path += req.url_; //"wwwroot/a/b/c.html"，在解析的时候加了，可以不要了
    //  // Util::ReadFile(req.path_, &body);//放入body中
    //  // bool ret = Util::ReadFile(req.path_, &body);

    // std::string response;
    // // 确定这个文件能不能打开，要写一个接口，如果不能打开，就把req的path_的路径改成404

    // if (true == Util::ReadFile(req.path_, &body)) // 因为有404，所以这样写，只要我们的ReadFile读取成功，
    // {
    //     response = "HTTP/1.0 200 OK" + SEP;
    //     response += "Content-Length: " + std::to_string(body.size()) + SEP;
    //     response += GetContentType(req.suffix_);
    //     response += SEP;
    //     response += body;
    // }
    // else
    // {
    //     response = "HTTP/1.0 404 Not Found" + SEP;
    //     Util::ReadFile(page_404, &body); // 把page_404导入body
    //     response += "Content-Length: " + std::to_string(body.size()) + SEP;
    //     response += GetContentType(".htlm");
    //     response += SEP;
    //     response += body;
    // }

    // std::string response = "HTTP/1.0 200 OK" + SEP;
    // response += "Content-Length: " + std::to_string(body.size()) + SEP;
    // // response += "Content-Type:text/html" + SEP;
    // response += GetContentType(req.suffix_);//根据后缀，封装一个GetContentType
    // response += SEP;
    // response += body;

    // 4、重定向测试
    // std::string response;
    // // response = "HTTP/1.0 302 Found" + SEP;
    // response = "HTTP/1.0 301 Moved Permanently" + SEP;
    // // response += "Location: https://www.qq.com/" + SEP;//重定向在腾讯的官网
    // response += "Location: https://www.baidu.com/" + SEP;
    // response += SEP;

    //5. cookie && session实验
    std::string response;
    response = "HTTP/1.0 200 OK" + SEP;
    response += "Set-Cookie: sessionid=1234abcd" + SEP;//任何人访问我，我都会把这个cookie写进去
    response += SEP;

    // if()//检查当前的session信息，写了个伪代码
    // request->sessionid;
    // sessions[sessionid]->status;
    // //合法就执行后面动作


    return response;

}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<HttpServer> tsvr(new HttpServer(HandlerHttp)); // TODO
    tsvr->InitServer();
    tsvr->Start();

    return 0;
}


//今天就会发现，每个网页由很多资源构成，当我们请求时，会给我们发很多http请求，
//一张网页里可能包含了20张图片，也许client就会发起20多次请求，http底层是tcp，每次访问资源，都要重新建立链接，
//建立链接成本太高了，每次建立连接传一张图片，断开连接，效率低，所以http1.0中有个选项，connection：keep-alive，常连接
//我们基于建立一个连接，一个请求，请求哪个资源就向这个公共的连接里塞一个请求，多个资源，就向这个连接塞多个请求，
//对方收到后，它会直接给多个请求依次处理，再返回多个响应，这就是常连接
//上次写的网络版本的计算器就是常连接，常连接的时候，要保证报头和有效载荷完全读完，不要影响下一次，
//而http它有自己的提取报头和有效载荷的方式，content-Length表示大小，所以一个报文完整读完，就不会影响下一个报文，所以http就天然具备常连接属性

//有时候会请求favicon.ico，这是网站的一个符号，像访问自己的网站有个黑圈圈，百度就有个熊掌图标，访问哔哩哔哩有它自己的图标，这就是favicon.ico

//在http中，我们所有的数据和报文都是不安全的，所以我们需要做下一件事情，如何保证在通信不发生安全问题呢，所以有了https